using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Net;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Threading;
using System.Windows.Threading;

namespace Faust.Andrew.MTG
{
    public abstract class Gatherer
    {
        private bool _isLocked;
        private object _threadLock;

        public event FindCardsCompleteHandler FindCardsCompleteEvent;

        //List of cards to be returned when the call is complete.
        //protected List<Card> _cards;
        //Search results to return when the call is complete.
        protected CardSearchResults _results;
        protected int _jobCount = 0;
        protected int _completedCount = 0;

        public Gatherer()
        {
            _threadLock = new object();
            _isLocked = false;
        }

        protected void FireFindCardsCompleteEvent(CardSearchResults results, object state)
        {
            if (FindCardsCompleteEvent != null)
            {
                FindCardsCompleteEvent(this, new FindCardsEventArgs(results, state));
            }
        }

        /// <summary>
        /// Checks to see if another thread has already taken the lock. This forces the Getherer object to only allow a single
        /// child thread to process. This is mainly for simplicity.
        /// </summary>
        /// <returns></returns>
        private bool GetLock()
        {
            //Local lock state variable that can be used outside the synchronized code
            bool lockState = true;

            //Lock on the private _threadLock object to ensure that only one thread is accessing the shared _isLocked field at a time.
            lock (_threadLock)
            {
                //Set the local lockState variable to the current state of the lock.
                lockState = _isLocked;

                //If lockState == false it means this thread can be allowed to take the lock
                if (!lockState)
                {
                    //Lock it so no other thread can
                    _isLocked = true;
                }
            }

            //If the local lockState is True it means another thread already has the lock. In this case return false to indicate
            //the lock is not available. If Lock State is false, it means the lock wasn't already taken by another thread so return
            //true to indicate this thread got the lock
            return (!lockState);
        }

        /// <summary>
        /// Release the thread lock.
        /// </summary>
        /// <remarks>This method needs to be used carefully. It does no checks to ensure the calling thread is the owner of the lock.</remarks>
        private void ReleaseLock()
        {
            //Synchronize access to the _isLocked field
            lock (_threadLock)
            {
                //Release the lock
                _isLocked = false;
            }
        }

        private void FindCardsThreaded(object state)
        {
            GathererThreadParameters parameters = (GathererThreadParameters)state;

            //We don't get details just yet as we're going to run the detail processing in their own threads.
            //List<Card> cards = FindCards(parameters.SearchValue, parameters.LoadDetails);
            _results = FindCards(parameters.SearchParameters, false);

            //If the details aren't supposed to be loaded then we're done processing. We release the locks, fire the complete
            //event and exit.
            if (!parameters.LoadDetails || _results == null || _results.Cards == null || _results.Cards.Count == 0)
            {
                ReleaseLock();
                parameters.CallingDispatcher.BeginInvoke(new Action<CardSearchResults, object>(FireFindCardsCompleteEvent), _results, parameters.CallerStateObject);
                return;
            }

            //If we get to this point it means we do have to load the details. We're going to run it in their own processes.
            _jobCount = _results.Cards.Count;
            _completedCount = 0;

            foreach (Card card in _results.Cards)
            {
                DetailsThreadParameters detailParams = new DetailsThreadParameters();
                detailParams.GathererThreadParameters = parameters;
                detailParams.Card = card;

                ThreadPool.QueueUserWorkItem(LoadCardDetailsAsync, detailParams);
            }
        }

        private void LoadCardDetailsAsync(object state)
        {
            DetailsThreadParameters parameters = (DetailsThreadParameters)state;
            LoadCardDetails(parameters.Card);

            lock (_threadLock)
            {
                _completedCount++;
                if (_completedCount >= _jobCount)
                {
                    //If this was the last job, we're complete. We can now signal the completion of the job
                    _isLocked = false;
                    parameters.GathererThreadParameters.CallingDispatcher.BeginInvoke(new Action<CardSearchResults, object>(FireFindCardsCompleteEvent), _results, parameters.GathererThreadParameters.CallerStateObject);
                }
            }
        }

        public void FindCardsAsync(CardSearchParameters searchParameters)
        {
            FindCardsAsync(searchParameters, true, null);
        }

        public void FindCardsAsync(CardSearchParameters searchParameters, bool loadDetails)
        {
            FindCardsAsync(searchParameters, loadDetails, null);
        }

        public void FindCardsAsync(CardSearchParameters searchParameters, bool loadDetails, object state)
        {
            //Retrieve the lock or throw an exception if the lock isn't available
            if (!GetLock())
            {
                throw new Exception("Another FindCardsAsync process is in progress. Only one instance of FindCardsAsync at a time is supported.");
            }

            GathererThreadParameters parameters = new GathererThreadParameters();
            parameters.CallingDispatcher = Dispatcher.CurrentDispatcher;
            parameters.CallerStateObject = state;
            parameters.LoadDetails = loadDetails;
            parameters.SearchParameters = searchParameters;

            ThreadPool.QueueUserWorkItem(FindCardsThreaded, parameters);
        }

        public abstract CardSearchResults FindCards(CardSearchParameters searchParameters);
        public abstract CardSearchResults FindCards(CardSearchParameters searchParameters, bool loadDetails);
        public abstract void LoadCardDetails(List<Card> cards);
        public abstract void LoadCardDetails(Card card);
        public Card LoadCard(string multiverseId)
        {
            Card card = new Card();
            card.MultiverseID = multiverseId;

            LoadCardDetails(card);

            return card;
        }
        public abstract Gatherer Clone();        
    }
}
